/*
Uninclude: Tool for detecting unused #included files in C and C++ code.
    Copyright (C) 2012  Daniel Gutson, FuDePAN

    Uninclude is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version./

    Uninclude is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Uninclude.  If not, see <http://www.gnu.org/licenses/>.

    This is the compiler driver.
*/

#ifndef COMPILER_DRIVER_H
#define COMPILER_DRIVER_H

#include <cstdlib>
#include <string>
#include <iostream>

using namespace std;

class CompilerDriver
{
    string otherOptions;

    enum LanguageMode
    {
        LanguageUnknown,
        LanguageCPP,
        LanguageC,
        LanguageSpecifiedInOptions
    };

    LanguageMode languageMode;

    static void strip(const string& file)
    {
        const int ret = system(("strip -s " + file ).c_str());
        if (ret == -1 || WEXITSTATUS(ret) != 0)
            throw "Error invoking strip.";
    }

    static string getFileExt(const string& fullPath)
    {
        const string::size_type last_dot = fullPath.find_last_of('.');
        if ( last_dot == string::npos )
            return string();
        else
            return fullPath.substr(last_dot + 1);
    }


    string getLanguageFlag() const
    {
        switch(languageMode)
        {
            case LanguageCPP:                   return "-x c++ ";
            case LanguageC:                     return "-x c ";
            case LanguageSpecifiedInOptions:    return string();
            case LanguageUnknown:
            default:                            throw "Internal: cannot compile a header without knowning the language.";
        }
    }

    void setLanguageFromFile(const string& file)
    {
        const string ext = getFileExt(file);
        static const string cppExtensions = "|cc|cp|cxx|cpp|CPP|c++|C|";

        if (ext == "c")
            languageMode = LanguageC;
        else if (cppExtensions.find("|" + ext + "|") != string::npos)
            languageMode = LanguageCPP;
    }

    static string redirection(bool suppressErrors)
    {
        return suppressErrors ? " 2>/dev/null" : string();
    }
public:
    CompilerDriver() : languageMode(LanguageUnknown) {}

    void addOption(const string& option)
    {
        otherOptions += option + " ";
        if (option == "-x")
            languageMode = LanguageSpecifiedInOptions;
    }

    bool compile(const string& cppfile, const string& outbin, bool suppressErrors)
    {
        bool ret = false;

        int ecode = system(("gcc -c -DNDEBUG " + otherOptions + cppfile + " -o " + outbin + redirection(suppressErrors)).c_str());

        if (languageMode == LanguageUnknown)
            setLanguageFromFile(cppfile);

        //cerr << "Compiling: " << cppfile << endl;

        if ( ecode == -1 )
            throw "Error invoking the compiler";
        else
        {
            ecode = WEXITSTATUS(ecode);
            ret = (ecode == 0);
            
            if ( ret )
                strip(outbin);
        }

        return ret;
    }

    bool compileHeader(const string& hfile, bool suppressErrors) const
    {
        bool ret = false;

        const int ecode = system(("gcc -c -DNDEBUG " + getLanguageFlag() + otherOptions + hfile + " -o /dev/null" + redirection(suppressErrors)).c_str());

        //cerr << "Compiling: " << cppfile << endl;

        if ( ecode == -1 )
            throw "Error compiling header file";
        else
            ret = (WEXITSTATUS(ecode) == 0);

        return ret;
    }

    void preprocess(const string& cppfile, const string& outpre) const
    {
        const int ret = system(("gcc -E " + otherOptions + cppfile + " > " + outpre).c_str());

        //cerr << "Preprocessing: " << cppfile << endl;

        if ( ret == -1 || WEXITSTATUS(ret) != 0)
            throw "Error invoking the preprocessor";
    }
};

#endif

